package free.mrchen.boot.gate.server.def;

import org.springframework.beans.factory.config.YamlProcessor;
import org.springframework.core.io.FileSystemResource;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

public class LocalStorageRouteDefFeature implements RouteDefFeature {

    private final String basePath;

    private final Map<String,RouteDef> routeDefsStable = new LinkedHashMap<>();

    private final Map<String,RouteDef> routeDefsFeature = new LinkedHashMap<>();

    private static final String ROUTE_DEF = "route-def";

    private static final String BACK_UP = "backup";

    private static final String CONFIG_FILE_SUFFIX = ".route-def.yml";

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss_SSS");

    public LocalStorageRouteDefFeature(String basePath ) {
        this.basePath = basePath;
    }

    public LocalStorageRouteDefFeature(String basePath , Map<String,RouteDef> initRouteDefs ) {
        this.basePath = basePath;
        routeDefsStable.putAll( initRouteDefs );
    }

    public Map<String, RouteDef> init() {
        try {
            routeDefsStable.clear();
            routeDefsFeature.clear();
            //从 {basePath}/route-def 下读取内容
            Files.walkFileTree( Paths.get( basePath , ROUTE_DEF ) ,new CustomReadConfigVisitor(routeDefsStable));
            //将stable填充至feature
            this.routeDefsFeature.putAll(this.routeDefsStable);
        } catch ( Exception e) {
            throw new RuntimeException(e);
        }
        return listStable();
    }

    @Override
    public Map<String,RouteDef> listStable() {
        return Collections.unmodifiableMap(routeDefsStable);
    }

    @Override
    public Map<String, RouteDef> listFeature() {
        return Collections.unmodifiableMap(routeDefsFeature);
    }

    @Override
    public boolean commitAll() {
        //将feature所有内容填充到stable
        routeDefsStable.clear();
        routeDefsStable.putAll( routeDefsFeature );
        return backupAndSaveToStorage();
    }

    @Override
    public boolean commit(String id) {
        //将指定id的feature填充到stable
        routeDefsStable.put( id , routeDefsFeature.get(id) );
        return backupAndSaveToStorage();
    }


    private boolean backupAndSaveToStorage() {
        try {
            final Path baseRouteDefPath = Paths.get( basePath , ROUTE_DEF );
            //将 {basePath}/route-def 下所有配置保存到 backup/{backupTime}/ 下
            Files.walkFileTree(  baseRouteDefPath  , new CustomBackupConfigVisitor( Paths.get( basePath , BACK_UP , LocalDateTime.now().format(FORMATTER) ) ) );
            //删除 {basePath}/route-def 下配置文件
            Files.walkFileTree( baseRouteDefPath , new CustomDeleteVisitor() );
            //将 routeDefs 内容输出到 {basePath}/route-def 下
            for (Map.Entry<String, RouteDef> entry : routeDefsStable.entrySet()) {
                RouteDef def = entry.getValue();
                if ( Boolean.TRUE.equals( def.getMetadata().get("removed") ) ) {
                    continue;
                }
                new CustomYamlProcessor( Paths.get( baseRouteDefPath.toString() , entry.getKey() + CONFIG_FILE_SUFFIX ) ).dump( entry.getValue() );
            }
            return true;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    @Override
    public boolean rollbackAll() {
        //直接把feature清空，然后重新覆盖
        this.routeDefsFeature.clear();
        this.routeDefsFeature.putAll( this.routeDefsStable);
        return true;
    }

    @Override
    public boolean rollback(String id) {
        this.routeDefsFeature.remove(id);
        if ( this.routeDefsStable.containsKey(id) ) {
            this.routeDefsFeature.put( id , this.routeDefsStable.get(id) );
        }
        return true;
    }

    @Override
    public boolean addOrReplace(RouteDef routeDef) {
        this.routeDefsFeature.put( routeDef.getId() , routeDef );
        return true;
    }

    @Override
    public boolean remove(String id) {
        RouteDef def = this.routeDefsFeature.get(id);
        if ( null != def ) {
            def.getMetadata().put("removed" , Boolean.TRUE );
        }
        return null != def;
    }


    static class CustomYamlProcessor extends YamlProcessor {

        private final Path resourcePath;

        private Properties properties = new Properties();

        CustomYamlProcessor(Path resourcePath) {
            this.resourcePath = resourcePath;
        }

        public RouteDef getRouteDef() {
            setResources(new FileSystemResource(resourcePath));
            process( ((properties, map) -> {
                this.properties.putAll(map);
            } ) );
            RouteDef routeDef = new RouteDef();
            routeDef.setId( String.valueOf( this.properties.get("id") ) );
            routeDef.setUri( String.valueOf( this.properties.get("uri") ) );
            routeDef.setOrder(Optional.ofNullable( this.properties.getProperty("order") ).map( Integer::valueOf ).orElse(Integer.MAX_VALUE)  );

            Object predicates = this.properties.get("predicates");
            if ( predicates instanceof Iterable ) {
                for (Object o : (Iterable<?>) predicates  ) {
                    routeDef.getPredicates().add(String.valueOf(o));
                }
            }

            Object filters = this.properties.get("filters");
            if ( filters instanceof Iterable ) {
                for (Object o : (Iterable<?>) filters  ) {
                    routeDef.getFilters().add(String.valueOf(o));
                }
            }

            Object metadata = this.properties.get("metadata");
            if ( metadata instanceof Map ) {
                for ( Map.Entry<?,?> entry : ((Map<?,?>)metadata).entrySet() ) {
                    routeDef.getMetadata().put( entry.getKey().toString() , entry.getValue() );
                }
            }



            return routeDef;
        }

        public void dump(RouteDef routeDef ) throws IOException {
            Files.write( resourcePath , Collections.singleton(createYaml().dumpAsMap( routeDef )) );
        }

    }

    /**
     * 加载
     */
    static class CustomReadConfigVisitor extends SimpleFileVisitor<Path> {

        private final Map<String,RouteDef> routeDefs;

        CustomReadConfigVisitor( Map<String,RouteDef> routeDefs) {
            this.routeDefs = routeDefs;
        }


        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            FileVisitResult r = super.visitFile(file, attrs);
            if ( file.toFile().isFile() && file.toFile().getName().endsWith(".route-def.yml") ) {
                RouteDef routeDef = new CustomYamlProcessor(file).getRouteDef();
                this.routeDefs.put( routeDef.getId() , routeDef);
            }
            return r;
        }
    }


    /**
     * 备份
     */
    static class CustomBackupConfigVisitor extends SimpleFileVisitor<Path> {

        private final Path saveToPath;

        CustomBackupConfigVisitor(Path saveToPath) {
            this.saveToPath = saveToPath;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            FileVisitResult r = super.visitFile(file, attrs);
            if ( isRouteConfig( file )  ) {
                saveToPath.toFile().mkdirs();
                Files.copy( file , Paths.get( saveToPath.toString() , file.toFile().getName() ) ,  StandardCopyOption.REPLACE_EXISTING );
            }
            return r;
        }


    }

    /**
     * 删除
     */
    static class CustomDeleteVisitor extends SimpleFileVisitor<Path> {

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            FileVisitResult r = super.visitFile(file, attrs);
            if ( isRouteConfig( file )  ) {
                Files.delete( file );
            }
            return r;
        }
    }

    private static boolean isRouteConfig( Path path ) {
        return null != path && path.toFile().isFile() && path.toFile().getName().endsWith(CONFIG_FILE_SUFFIX);
    }


}
